package hermesV3.services.HubService;

import java.util.HashSet;
import java.util.Set;

import it.unicam.cs.cosy.bioshape.core.util.Messages;
import it.unicam.cs.cosy.bioshape.gui.Monitor;
import hermesV3.Message;
import hermesV3.agent.CommunicationException;
import hermesV3.agent.Identifier;
import hermesV3.agent.InteractionContainer;
import hermesV3.agent.UserAgent;
import hermesV3.protocols.SynProtocol;
import hermesV3.services.HubAgent;
import hermesV3.util.HermesLogger;

/**
 * Created by the user when a simulation is started.<br>
 * Manages the simulation, from the GUI to the simulation nodes and vice versa.
 * Agents are expected to communicate with this agents with two type of
 * interactions: {@code syn} interaction and {@code ack} interaction. The former
 * is used to send information to the {@code SimulationAgent}, whereas the
 * latter is used to communicate errors (and thus termination of the
 * simulation). <br>
 * Since the simulation must be started on ALL the nodes which participate in
 * it, a {@code SimulationAgent} is launched on each simulation node. However,
 * the one launched on the {@code GlobalCoordinator} node act as information
 * about all the simulation nodes. Thus, it is the one elected for controlling
 * all the simulation nodes. //TODO rivedere sto commento che è meglio! Magari
 * non sarà più così.
 * 
 * @author Federico Buti
 */
public class SimulationAgent extends UserAgent {

	private static final long serialVersionUID = -6397721852552333662L;
	// Gui of the simulator
	private Monitor gui;
	// Other SimulationAgents in other running nodes
	private Set<Identifier> idPeers = new HashSet<Identifier>();
	// Local Coordinators
	private Set<Identifier> idLCs = new HashSet<Identifier>(); 
	private Identifier GC; // Global Coordinator
	private Identifier SM; // Space Manager
	private Identifier serviceId;
	private boolean simulate = true;
	private boolean toRestart;
	private int it = 0;

	/**
	 * A {@link SimulationAgent} with a name. Sets also a reference to itself in
	 * the {@code Monitor} so that the latter can send messages to the
	 * {@code SimulationAgent} on its behalf.
	 * 
	 * @param gui
	 *            reference to the GUI frame to update its status as the
	 *            simulation evolve
	 */
	public SimulationAgent(Monitor gui) {
		super("SimulationAgent");
		this.gui = gui;
		gui.setAgentref(this);
	}

	@Override
	public void init() {
		reception();
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - Input agent correctly initiated. Contacting HUB "
				+ "for simulation environment set-up...");
		// Connection attempts to the HUB service...
		serviceId = this.getServiceIdentifier("HUB");
		if (serviceId == null) {
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - Something wrong with the HUB service agent...");
			HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
					+ " - Please review your configuration..."
					+ "simulation aborted...");
		}
		expectInput();
	}

	/**
	 * The {@link SimulationAgent} communicates to the {@link HubAgent} the
	 * simulation file which the user selected from the GUI to be simulated. <br>
	 * After the it started the simulation waits for messages from the GUI or
	 * from the simulation agents.
	 */
	private void expectInput() {
		while (true) {
			// eats up previous messages for termination!
			Message<?> msg = this.getMessageSynch();
			if (!msg.getSenderAgentId().equals(this.getIdentifier())) {
				System.out.println(msg.getSenderAgentId().getName());
				continue;
			}
			// Messages.receiveSyn(this, SimulationAgent.class);
			String file = (String) ((InteractionContainer) msg.getObject())
					.getParameter("syn");
			SynProtocol synProt = (SynProtocol) getServiceProtocol(serviceId);
			InteractionContainer ic = new InteractionContainer(synProt);
			ic.makeInteraction(SynProtocol.INTERACTIONSYN, file);
			try {
				sendMessageToServiceAgent(new Message<InteractionContainer>(
						getIdentifier(), serviceId, ic));
			} catch (CommunicationException e) {
				e.printStackTrace();
			}
			//
			manageSimulation();
		}
	}

	/**
	 * It manages the synchronization between the GUI and the simulation. It
	 * receives messages from the simulation agents and the GUI respectively to
	 * alter the status of the simulation (start-stop-pause it) or to update the
	 * GUI status according to the status of the simulation (e.g. if it is
	 * aborted) prematurely.
	 */
	private void manageSimulation() {
		while (true) {
			Message<?> msg = getMessageSynch();
			String sender = msg.getSenderAgentId().getName();
			InteractionContainer ic = (InteractionContainer) msg.getObject();
			System.out.println("sender: " + sender);
			//
			// HubAgent MESSAGES
			if (sender.equals("HubAgent")) {
				if (ic.getInteractionName().equals(
						SynProtocol.INTERACTIONSYNACK)) {
					Identifier[] ids = (Identifier[]) ic
							.getParameter(SynProtocol.INTERACTIONSYNACK);
					GC = ids[0];
					SM = ids[1];
				} else {
					System.out.println("ROLLBACK HUB");
					resetSimulation();
					return;
				}
			}
			// PeerAgent MESSAGES
			if (sender.equals("PeerAgent")) {
				if (ic.getInteractionName().equals(SynProtocol.INTERACTIONSYN)) {
					idPeers.add(msg.getSenderAgentId());
				} else {
					System.out.println("ROLLBACK PEER");
					resetSimulation();
					return;
				}
			}
			// GlobalCoordinator MESSAGES
			if (sender.equals("GlobalCoordinator")) {
				if (ic.getInteractionName().equals(SynProtocol.INTERACTIONSYN))
					idPeers.add((Identifier) ic.getParameter("syn"));
				else if (ic.getInteractionName().equals(
						SynProtocol.INTERACTIONSYNACK)) {
					if (simulate) {
						System.out.println("Iteration (SA) " + ++it);
						Messages.sendSynAck(this, GC);
					} else
						toRestart = true; // keep track of the restarting case
				} else {
					System.out.println("ROLLBACK GC");
					resetSimulation();
					return;
				}
			}
			// LocalCoordinator MESSAGES
			if (sender.equals("LocalCoordinator")) {
				// ACK only errors from LCs
				if (ic.getInteractionName().equals(SynProtocol.INTERACTIONACK)) {
					System.out.println("ROLLBACK LC");
					resetSimulation();
					return;
				}
			}
			// SpaceManager MESSAGES
			if (sender.equals("SpaceManager")) {
				if (ic.getInteractionName().equals(SynProtocol.INTERACTIONSYN))
					idPeers.add((Identifier) ic.getParameter("syn"));
				else {
					System.out.println("ROLLBACK SM");
					resetSimulation();
					return;
				}
			}
			// SimlationAgent MESSAGES (GUI messages)
			if (sender.equals("SimulationAgent")) {
				if (ic.getInteractionName().equals(SynProtocol.INTERACTIONSYN)) {
					if (!simulate && toRestart) {
						toRestart = false;
						Messages.sendSynAck(this, GC);
					}
					simulate = !simulate; // play - pause status
				} else {
					System.out.println("ROLLBACK MYSELF! ;-)");
					resetSimulation(); // stop status
					return;
				}
			}
		}
	}

	/**
	 * Utility method. Resets the GUI and the {@code Place}: the icons in the
	 * GUI are set to the base state and all the support {@code Agent}s created
	 * are destroyed (if still running).
	 */
	private void resetSimulation() {
		// communicate with PeerAgents
		for (Identifier idP : idPeers) {
			if (isActive(idP))
				Messages.sendSyn(this, idP);
		}
		idPeers.clear();
		// communicate with LocalCoordinators
		for (Identifier idL : idLCs) {
			if (isActive(idL))
				Messages.sendSyn(this, idL);
		}
		idLCs.clear();
		// communicate with SpaceManager
		if (isActive(SM))
			Messages.sendSyn(this, SM);
		// communicate with GlobalCoordinator
		if (isActive(GC))
			Messages.sendSyn(this, GC);
		// communicate with the HubAgent
		Messages.sendServiceInteraction(this, serviceId, new SynProtocol(),
				SynProtocol.INTERACTIONACK);
		// resetting flags: simulation active and nothing to restart
		simulate = true;
		toRestart = false;
		it = 0;
		//
		// finalizing termination on the GUI and communicating it via log
		gui.simulationEnd();
		HermesLogger.log(HermesLogger.INFO, this.getAgentName()
				+ " - Ready to set up a new simulation environment...");
	}

	/**
	 * Checks if the {@code Agent} associated with the passed
	 * {@code Identifier} is still registered in the {@code BasicServices}
	 * layer. Used to avoid the sending of messages to destroyed agents.
	 * 
	 * @param id
	 *            The {@code Identifier} to be checked.
	 * @return {@code true} if the {@code Agent} is still active, {@code false}
	 *         if it has been destroyed.
	 */
	private boolean isActive(Identifier id) {
		return id != null
				&& basicServices.getReceiveInterface().isAgentRegistered(id);
	}
}